EDA von Gebrauchtwagenangeboten der Website “Autoscout24”

Von Annika Scheug und Oliver Schabe, Vorlesung “Python for Data Science”, Sommersemester 2022

Einleitung

In diesem Projekt wird eine Explorative Datenanalyse von Gebrauchtwagenangeboten der Website “Autoscout24” durchgeführt.
Autoscout24 (https://www.autoscout24.de/) ist eine Online-Plattform zum Kauf und Verkauf von Neu- und Gebrauchtwagen.
Die Startseite von Autoscout24 sieht folgendermaßen aus:
Autoscout24 Startseite

Die Suchergebnisse werden in einer Liste dargestellt, wobei immer 20 Fahrzeuge pro Seite angezeigt werden und je Suchergebniss 20 Seiten:
Autoscout24 Suche

Ziel des Projektes ist es, Daten über Zustand und Ausstattung verschiedener Fahrzeuge aus dem Quellcode der Website abzuziehen und diese Daten anschließend ausführlich zu analysieren. Bei der Analyse der Daten sollen mögliche Korrelationen zwischen den in den Angeboten angegebenen Werten untersucht werden. Der Schwerpunkt soll dabei auf dem Preis und den Fahrzeugeigenschaften liegen, die einen relevanten Einfluss auf den Verkaufspreis haben (wie möglicherweise Kilometerstand, Alter des Fahrzeuges etc.).
Als möglicher praktischer Anwendungsfall der hier gesammelten Erkentnisse könnte beispielsweise ein Tool sein, welches interessierten Käufern oder Verkäufern realistische Preisvorschläge auf Basis von Angaben zum Fahrzeugszustand macht.
Die Entwicklung einer solchen Applikation ist jedoch nicht im Scope dieses Projektes.

Import

Zunächst werden alle für dieses Projekt benötigten Packages und Bibliotheken importiert.

#Basics
import pandas as pd
import numpy as np

#Webcrawling
#pip install beautifulsoup4
from bs4 import BeautifulSoup
import requests

#Deactivate warnings
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
#Data visualization
import matplotlib.pyplot as plt

import plotly.express as px
import plotly.graph_objs as go
import plotly.io as pio
import plotly.figure_factory as ff
import plotly.offline as pyo
# Set notebook mode to work in offline
pyo.init_notebook_mode()

import seaborn as sns 

np.set_printoptions(precision=6)
np.set_printoptions(suppress=True)
pd.set_option("display.max_columns", 200)
pd.set_option("display.max_rows", 200)
#Geo visualization
import folium
#!pip install geopy
from geopy.geocoders import Nominatim

#Postgre SQL
import psycopg2 
import json 
from sqlalchemy import create_engine 

#Regression
import statsmodels.formula.api as smf

Webcrawling & Creation of Dataframe

Als Erstes ist es notwendig, die Fahrzeugdaten von der Webseite Autoscout24 zu crawlen und in einem Dataframe zu speichern.

Für das Crawlen der Daten wird die Methode extractPageCarDF definiert. Dieser muss beim Aufruf die Variable URL mitgegeben werden. Dabei handelt es sich um den Link zu einem Autoscout24 Suchergebnis, welches stehts 20 Autos beinhaltet (sofern sie den Suchkriterien entsprechen).
Jedes Auto wird dabei vom HTML Element Article umschlossen. Daher wird eine Schleife implementiert, welche für jedes Auto im Suchergebnis die nachfolgenden Daten aus den dazugehörigen HTML Elementen der Webseite extrahiert:

  • Titel

  • Fahrzeugversion

  • Untertitel

  • Preis

  • Leasingpreis

  • Fahrzeugstandort

Falls eines der HTML-Elemente nicht gefunden werden kann, wird jede der Anweisungen durch einen try except Block umschlossen. Falls kein Eintrag gefunden wird, wird der jeweilige Wert mit einem NULL-Wert belegt.

Eine Besonderheit ist zudem der Preis. Wird das Element ListItem_pricerow gefunden, handelt es sich um einen Verkaufspreis und kein Leasingangebot. Leasing wird daher False gesetzt. Wird dieses Element nicht gefunden, sondern LeasingPrice_price handelt es sich um ein Leasing Angebot.

Diese Daten werden dem Dataframe pageCarDF hinzugefügt.

In einer weiteren Schleife werden die folgenden Fahrzeugdetaildaten aus dem HTML Div Container VehicleDetailTable abgezogen:
Autoscout24 VehicleDetailTable
Es wird zunächst ein leeres Dataframe initialisiert. In einer Schleife wird für jedes Fahrzeug die leere Liste VehicleDetailList erzeugt und dieser in einer inneren Schleife jedes Element der VehicleDetailTable hinzugefügt.
Die Liste wird anschließend dem VehicleDetailDF hinzugefügt. Hierfür ist ein try except notwendig. Leasing Fahrzeuge haben ein zweites Element namens VehicleDetailTable, welches allerdings nur 3 Einträge zum Themengebiet Leasing hat. Der Versuch diese Listen mit 3 Einträgen dem VehicleDetailDF hinzuzufügen, läuft aufgrund nicht passender Längen auf Fehler. Dieser Fehler wird im except Block bewusst mit einem Continue abgefangen. Die Leasing VehicleDetailLists werden nicht weiter benötigt und fallen somit raus.

Nun liegt das pageCarDF und das VehicleDetailDF vor, welche beide je Fahrzeug eine Zeile beinhalten.
Die beiden Dataframes werden mithilfe der merge-Methode über den Index gejoined.

Die Methode gibt das Dataframe pageCarDF als return value zurück. Diese beinhaltet alle relevanten Daten von den Fahrzeugen einer Suchergebnisseite (in der Regel 20 Fahrzeuge).

def extractPageCarDF(URL):

    soup=BeautifulSoup(requests.get(URL).text,"html.parser")
    pageCarDF=pd.DataFrame()

    for car in soup.findAll("article"):
        data = car.find("div", {"class": lambda L: L and L.startswith("ListItem_wrapper")})
        try:
            header = data.find("h2").text
        except:
            header = np.NaN
        try:
            version = data.find("span", {"class": lambda L: L and L.startswith("ListItem_version")}).text
        except:
            version = np.NaN
        try:
            subtitle = data.find("span", {"class": lambda L: L and L.startswith("ListItem_subtitle")}).text
        except:
            subtitle = np.NaN
        try:
            #Versuch Preis Element zu finden
            price = data.find("div", {"class": lambda L: L and L.startswith("ListItem_pricerow")}).text
            leasing = False
        except:
            #wenn oberes Element nicht gefunden werden kann, handelt es sich um einen Leasing Wagen, mit dem nachfolgenden HTML Element
            price = data.find("span", {"class": lambda L: L and L.startswith("LeasingPrice_price")}).text
            leasing = True 

        try:
            location = car.find("span", {"style": lambda L: L and L.startswith("grid-area:address")}).text
        except:
            location = np.NaN

        #Daten dem pageCarDF hinzufügen
        pageCarDF = pageCarDF.append({"Titel":header, "Version":version, "Untertitel":subtitle, "Preis":price, "Leasing":leasing, "Standort":location}, ignore_index=True)


    #VehicleDetailTable
    VehicleDetailDF = pd.DataFrame()
    for car in soup.findAll("div" , {"class":"VehicleDetailTable_container__mUUbY"}):
        VehicleDetailList = []
        for c in car:
            VehicleDetailList.append(c.text)
        try:
            VehicleDetailDF = VehicleDetailDF.append({"km":VehicleDetailList[0], "Erstzulassung":VehicleDetailList[1], "PS":VehicleDetailList[2], "Zustand":VehicleDetailList[3], "Fahrzeughalter":VehicleDetailList[4], "Getriebe":VehicleDetailList[5], "Kraftstoff": VehicleDetailList[6], "Verbrauch_l_pro_100km":VehicleDetailList[7], "Emissionen_g_pro_km":VehicleDetailList[8]}, ignore_index=True)
        except:
            continue #VehicleDetailLists mit Länge 3 sind extra VehicleDetailTables, die nur bei Leasing Wagen vorkommen. Diese sollen nicht übernommen werden, daher Continue
    
    #Join pageCarDF und VehicleDetailDF
    pageCarDF = pd.merge(pageCarDF, VehicleDetailDF, left_index=True, right_index=True)   

    return pageCarDF

Die Methode extractPageCarDF gilt es nun mit den passenden Paramentern aufzurufen.

Es werden zunächst zwei leere Dataframes initialisiert.
Die Suche auf Autoscout24 wurde zunächst komplett ohne Filter aufgerufen. Pro Suchergebnis gibt die Webseite ingesamt 20 Suchergebnisseiten mit jeweils 20 Fahrzeugen aus. Somit können mit einer Suche maximal 20 * 20 = 400 Autos von der Webseite gecrawlt werden.
Da für die Analysen im Projekt mehr als 400 Datensätze gewünscht sind, wird ein Filter “Erstzulassung von” (fregfrom) und “Erstzulassung bis” (fregto) gesetzt. Die Jahreszahlen werden in der Liste fregtoList von 1990 bis 2022 in 1 Jahresschritten gewählt.

In einer Schleife wird zunächst der Filter auf die jeweilige Jahreszahl gesetzt. In einer inneren Schleife wird jeweils die Ergebnisseite der Suche festgelegt. Somit werden pro Iteration der äußeren Schleife 20 Seiten des Suchergebnisses gecrawlt.
Dafür wird zunächst die URL aus “Erstzulassung von” fregfrom= , “Erstzulassung bis” fregto= und Suchergebnisseite page erstellt. Die URL wird an die Methode extractPageCarDF übergeben und diese ausgeführt.
Das resultierende Dataframe pageCarDF mit 20 Fahrzeugen wird dem Dataframe AutoDFraw angehängt. Anschließend wird die Methode für die nächste Seite im Suchergebnis ausgeführt und das Ergebnis wieder AutoDFraw hinzufügt.
Dataframe pageCarDF wird somit bei jeder Ausführung der Methode extractPageCarDF neu erstellt, während Dataframe AutoDFraw immer weiter wächst.

Die Methode wird für jeden Filter “Erstzulassung bis” für 20 Suchergebnisseiten ausgeführt, sodass das Dataframe AutoDFraw am Ende über 6000 Einträge enhält.

AutoDFraw=pd.DataFrame()
pageCarDF=pd.DataFrame()
baselink = "https://www.autoscout24.de/lst?fregfrom="
fregList = list(range(1990, 2022, 1))

for freg in fregList:
    for page in range(20):
        URL = baselink + str(freg) + "&fregto=" + str(freg) + "&page=" + str(page)
        pageCarDF = extractPageCarDF(URL)
        AutoDFraw=pd.concat([AutoDFraw, pageCarDF],axis=0, ignore_index=True)
AutoDFraw
Titel Version Untertitel Preis Leasing Standort km Erstzulassung PS Zustand Fahrzeughalter Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km
0 Volkswagen Golf II GL Tuningfahrz. für Schrauber rostfrei viel... NaN € 2.950,-Keine Angabe False DE-92318 Neumarkt 139.800 km 06/1990 66 kW (90 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.)
1 Renault Alpine A310 2.5 Turbo V6 NaN € 23.900,- False Contáctanos en: • ES-27003 LUGO 73.000 km 01/1990 147 kW (200 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km)
2 Mercedes-Benz 190 190E 2.5-16 NaN € 31.500,- False Contáctanos en: • ES-15800 MELIDE 90.000 km 03/1990 143 kW (194 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km)
3 BMW Z1 2,7 Ltr.+BBS+RESTAURIERT+HARDTOP NaN € 73.900,-Keine Angabe False Ihr Verkaufsteam • DE-49076 Osnabrück 75.156 km 06/1990 150 kW (204 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.)
4 Alfa Romeo Spider 2.0 Quadrifoglio Verde NaN € 19.900,- False Contáctanos en: • ES-46006 VALENCIA 79.000 km 09/1990 94 kW (128 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km)
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
12174 Audi A3 advanced 40TFSI S-line PDC SMARTINT Käsmann - die große Auswahl im Neckar-Odenwald... € 37.930,-Keine Angabe False Audi: 06261-9282-555 VW+Skoda: 06261-9730-555 ... 2.653 km 12/2021 140 kW (190 PS) Gebraucht 1 Fahrzeughalter Automatik Benzin - (l/100 km) - (g/km)
12175 Volkswagen Golf VIII 1.5 TSI OPF -Active- 6-Gang Navi/ SHZ Standheizung, Navigationssystem, 3-Zonen-Klima... € 25.480,-Sehr guter Preis False Kundenkontaktcenter • DE-24944 Flensburg 8.400 km 08/2021 96 kW (131 PS) Gebraucht 1 Fahrzeughalter Schaltgetriebe Benzin 5,1 l/100 km (komb.) 117 g/km (komb.)
12176 Kia Ceed / cee'd Ceed Sportswagon Spirit Rückfahrkamera Ihr Kia und Mitsubishi Vertragshändler € 24.489,-Fairer Preis False DE-39576 Stendal 19.700 km 05/2021 103 kW (140 PS) Gebraucht 1 Fahrzeughalter Schaltgetriebe Benzin 146 g/km (komb.)
12177 BMW 318 iA Lim. AHK|Glasdach|Navi|HUD|LED|17"LM|SHZ Schiebedach, Einparkhilfe Kamera, Anhängerkupp... € 33.699,-Sehr guter Preis False - - • DE-46149 Oberhausen 12.103 km 01/2021 115 kW (156 PS) Gebraucht 1 Fahrzeughalter Automatik Benzin 6,3 l/100 km 142 g/km (komb.)
12178 Dacia Spring Business NaN € 22.985,-Keine Angabe False Ihr Team der BOB Automobile • DE-45141 Essen 3.551 km 07/2021 33 kW (45 PS) Gebraucht 1 Fahrzeughalter Automatik Elektro - (l/100 km) 0 g/km (komb.)

12179 rows × 15 columns

Abschließend werden die gecrawlten Daten in einer postgreSQL Datenbank gesichert, sodass nicht bei jeder Programmausführung die Daten neu gecrawlt werden müssen.

Dafür muss zunächst eine Verbindung mit der Datenbank hergestellt werden. Das eingelesene JSON-File enthält die Datenbank-Parameter und muss von jedem Anwender mit seinen Zugangsdaten befüllt und im gleichen Dateipfad wie dieses Juypter-Notebook abgespeichert werden.

Das JSON-File benötigt folgende Informationen im gezeigten Format:
configLocalDS.json

#import json file for database connection parameters
with open('configLocalDS.json') as f:
    conf = json.load(f)

Anschließend wird unter Verwendung von sqlalchemy eine Verbindung zur Datenbank hergestellt.

conn_str ='postgresql://%s:%s@localhost:5432/%s'%(conf["user"], conf["passw"],conf["database"])
engine = create_engine(conn_str)

Das Dataframe wird nun in der Tabelle autoscout24cars in postgre SQL gespeichert, sofern die Tabelle noch nicht vorhanden ist.

if not engine.has_table("autoscout24cars"):
    AutoDFraw.to_sql(name='autoscout24cars',index=True, index_label='index',con=engine)
else:
    print("table already exists")
C:\Users\Admin\AppData\Local\Temp/ipykernel_6452/3504836538.py:1: SADeprecationWarning:

The Engine.has_table() method is deprecated and will be removed in a future release.  Please refer to Inspector.has_table(). (deprecated since: 1.4)
table already exists

Als Alternative zu SQL können die Daten in Excel gespeichert und wieder eingelesen werden um einen gleichbleibenden Datenstand zur Analyse zu gewährleisten:
Diese Codezeile ist hier auskommentiert, um das Backup nur bewusst überschreiben zu können.

#AutoDFraw.to_excel("AutoDF_raw.xlsx")

Die Fahrzeugdaten von Autoscout24 wurden erfolgreich abgezogen und in einem Dataframe gespeichert. Allerdings entsprechen viele Spalten noch nicht dem gewünschten Format, da beispielsweise Sonderzeichen enthalten sind oder numerische Werte nicht als solche erkannt werden.
Aus diesem Grund muss das Dataframe nun so bearbeitet werden, dass alle Spalten in einer für die Explorative Datenanalyse sinnvollen Struktur vorliegen.

Zunächst werden die in SQL gesicherten Daten in ein neues Dataframe AutoDF geladen.

AutoDF = pd.read_sql_query('SELECT * FROM autoscout24cars',engine, index_col="index")
AutoDF
Titel Version Untertitel Preis Leasing Standort km Erstzulassung PS Zustand Fahrzeughalter Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km
index
0 Volkswagen Golf II GL Tuningfahrz. für Schrauber rostfrei viel... None € 2.950,-Keine Angabe False DE-92318 Neumarkt 139.800 km 06/1990 66 kW (90 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.)
1 Renault Alpine A310 2.5 Turbo V6 None € 23.900,- False Contáctanos en: • ES-27003 LUGO 73.000 km 01/1990 147 kW (200 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km)
2 Mercedes-Benz 190 190E 2.5-16 None € 31.500,- False Contáctanos en: • ES-15800 MELIDE 90.000 km 03/1990 143 kW (194 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km)
3 BMW Z1 2,7 Ltr.+BBS+RESTAURIERT+HARDTOP None € 73.900,-Keine Angabe False Ihr Verkaufsteam • DE-49076 Osnabrück 75.156 km 06/1990 150 kW (204 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.)
4 Alfa Romeo Spider 2.0 Quadrifoglio Verde None € 19.900,- False Contáctanos en: • ES-46006 VALENCIA 79.000 km 09/1990 94 kW (128 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km)
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
12174 Audi A3 advanced 40TFSI S-line PDC SMARTINT Käsmann - die große Auswahl im Neckar-Odenwald... € 37.930,-Keine Angabe False Audi: 06261-9282-555 VW+Skoda: 06261-9730-555 ... 2.653 km 12/2021 140 kW (190 PS) Gebraucht 1 Fahrzeughalter Automatik Benzin - (l/100 km) - (g/km)
12175 Volkswagen Golf VIII 1.5 TSI OPF -Active- 6-Gang Navi/ SHZ Standheizung, Navigationssystem, 3-Zonen-Klima... € 25.480,-Sehr guter Preis False Kundenkontaktcenter • DE-24944 Flensburg 8.400 km 08/2021 96 kW (131 PS) Gebraucht 1 Fahrzeughalter Schaltgetriebe Benzin 5,1 l/100 km (komb.) 117 g/km (komb.)
12176 Kia Ceed / cee'd Ceed Sportswagon Spirit Rückfahrkamera Ihr Kia und Mitsubishi Vertragshändler € 24.489,-Fairer Preis False DE-39576 Stendal 19.700 km 05/2021 103 kW (140 PS) Gebraucht 1 Fahrzeughalter Schaltgetriebe Benzin None 146 g/km (komb.)
12177 BMW 318 iA Lim. AHK|Glasdach|Navi|HUD|LED|17"LM|SHZ Schiebedach, Einparkhilfe Kamera, Anhängerkupp... € 33.699,-Sehr guter Preis False - - • DE-46149 Oberhausen 12.103 km 01/2021 115 kW (156 PS) Gebraucht 1 Fahrzeughalter Automatik Benzin 6,3 l/100 km 142 g/km (komb.)
12178 Dacia Spring Business None € 22.985,-Keine Angabe False Ihr Team der BOB Automobile • DE-45141 Essen 3.551 km 07/2021 33 kW (45 PS) Gebraucht 1 Fahrzeughalter Automatik Elektro - (l/100 km) 0 g/km (komb.)

12179 rows × 15 columns

Alternativ, falls die Verbindung zu postgreSQL scheitert, können die Daten auch aus dem Backup Excel geladen werden. Dieser Code ist aktuell auskommentiert und muss bei Bedarf aktiviert werden.

AutoDF=pd.read_excel("AutoDF_raw.xlsx",index_col=0)

Feature Engineering

Erzeugung zusätzlicher Variablen

Automarke

Eine für die Datenanalyse interessante Information ist die Automarke. Diese ist im Titel der Anzeige als erstes Wort enthalten. Daher wird zur Bestimmung der Automarke das erste Wort der Spalte Titel extrahiert und in einer neuen Spalte Marke gespeichert. Falls eine Automarke aus mehr als einem Wort besteht, wird lediglich das erste Wort übernommen.

# Erzeugen der Spalte "Marke" aus den Informationen der Spalte "Titel"
AutoDF['Marke'] = AutoDF['Titel'].str.split('\s+').str[0]

Ausstattung

In der Spalte Untertitel werden Ausstattungsmerkmale des Fahrzeugs aufgezählt. Einige ausgewählte Austattungsmerkmale werden als extra Spalten in das Dataframe aufgenommen. Dafür wird folgende Annahme getroffen: Ein Fahrzeug besitzt eine bestimmte Ausstattung, wenn diese in Spalte Untertitel erwähnt wird. Wird diese dort nicht erwähnt, besitzt ein Fahrzeug diese Ausstattung nicht. Dies wird mithilfe der Methode str.contains geprüft.

# Erzeugung zusätzlicher Variablen "Ausstattung" 
AutoDF['Alufelgen']= AutoDF['Untertitel'].str.contains("Alufelgen")
AutoDF['Sitzheizung']= AutoDF['Untertitel'].str.contains("Sitzheizung")
AutoDF['Klimaanlage']= (AutoDF['Untertitel'].str.contains("Klimaanlage")) | (AutoDF['Untertitel'].str.contains("Klimaautomatik"))
AutoDF['Einparkhilfe']= AutoDF['Untertitel'].str.contains("Einparkhilfe ")
AutoDF['Navigationssystem']= AutoDF['Untertitel'].str.contains("Navigationssystem")
AutoDF.head()
Titel Version Untertitel Preis Leasing Standort km Erstzulassung PS Zustand Fahrzeughalter Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km Marke Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem
0 Volkswagen Golf II GL Tuningfahrz. für Schrauber rostfrei viel... NaN € 2.950,-Keine Angabe False DE-92318 Neumarkt 139.800 km 06/1990 66 kW (90 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.) Volkswagen NaN NaN False NaN NaN
1 Renault Alpine A310 2.5 Turbo V6 NaN € 23.900,- False Contáctanos en: • ES-27003 LUGO 73.000 km 01/1990 147 kW (200 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km) Renault NaN NaN False NaN NaN
2 Mercedes-Benz 190 190E 2.5-16 NaN € 31.500,- False Contáctanos en: • ES-15800 MELIDE 90.000 km 03/1990 143 kW (194 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km) Mercedes-Benz NaN NaN False NaN NaN
3 BMW Z1 2,7 Ltr.+BBS+RESTAURIERT+HARDTOP NaN € 73.900,-Keine Angabe False Ihr Verkaufsteam • DE-49076 Osnabrück 75.156 km 06/1990 150 kW (204 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.) BMW NaN NaN False NaN NaN
4 Alfa Romeo Spider 2.0 Quadrifoglio Verde NaN € 19.900,- False Contáctanos en: • ES-46006 VALENCIA 79.000 km 09/1990 94 kW (128 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km) Alfa NaN NaN False NaN NaN

Geodaten

Aus der Spalte Standort wird nun der Stadtname extrahiert, um eine spätere Kartendarstellung des Fahrzeugstandorts zu ermöglichen. Dies ist immer das letzt Wort der Spalte.

#Stadtname
AutoDF['Stadt'] = AutoDF['Standort'].str.split(' ').str[-1]
AutoDF.head()
Titel Version Untertitel Preis Leasing Standort km Erstzulassung PS Zustand Fahrzeughalter Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km Marke Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem Stadt
0 Volkswagen Golf II GL Tuningfahrz. für Schrauber rostfrei viel... NaN € 2.950,-Keine Angabe False DE-92318 Neumarkt 139.800 km 06/1990 66 kW (90 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.) Volkswagen NaN NaN False NaN NaN Neumarkt
1 Renault Alpine A310 2.5 Turbo V6 NaN € 23.900,- False Contáctanos en: • ES-27003 LUGO 73.000 km 01/1990 147 kW (200 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km) Renault NaN NaN False NaN NaN LUGO
2 Mercedes-Benz 190 190E 2.5-16 NaN € 31.500,- False Contáctanos en: • ES-15800 MELIDE 90.000 km 03/1990 143 kW (194 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km) Mercedes-Benz NaN NaN False NaN NaN MELIDE
3 BMW Z1 2,7 Ltr.+BBS+RESTAURIERT+HARDTOP NaN € 73.900,-Keine Angabe False Ihr Verkaufsteam • DE-49076 Osnabrück 75.156 km 06/1990 150 kW (204 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) 0 g/km (komb.) BMW NaN NaN False NaN NaN Osnabrück
4 Alfa Romeo Spider 2.0 Quadrifoglio Verde NaN € 19.900,- False Contáctanos en: • ES-46006 VALENCIA 79.000 km 09/1990 94 kW (128 PS) Gebraucht - (Fahrzeughalter) Schaltgetriebe Benzin - (l/100 km) - (g/km) Alfa NaN NaN False NaN NaN VALENCIA
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
12174 Audi A3 advanced 40TFSI S-line PDC SMARTINT Käsmann - die große Auswahl im Neckar-Odenwald... € 37.930,-Keine Angabe False Audi: 06261-9282-555 VW+Skoda: 06261-9730-555 ... 2.653 km 12/2021 140 kW (190 PS) Gebraucht 1 Fahrzeughalter Automatik Benzin - (l/100 km) - (g/km) Audi False False False False False Mosbach
12175 Volkswagen Golf VIII 1.5 TSI OPF -Active- 6-Gang Navi/ SHZ Standheizung, Navigationssystem, 3-Zonen-Klima... € 25.480,-Sehr guter Preis False Kundenkontaktcenter • DE-24944 Flensburg 8.400 km 08/2021 96 kW (131 PS) Gebraucht 1 Fahrzeughalter Schaltgetriebe Benzin 5,1 l/100 km (komb.) 117 g/km (komb.) Volkswagen False False True True True Flensburg
12176 Kia Ceed / cee'd Ceed Sportswagon Spirit Rückfahrkamera Ihr Kia und Mitsubishi Vertragshändler € 24.489,-Fairer Preis False DE-39576 Stendal 19.700 km 05/2021 103 kW (140 PS) Gebraucht 1 Fahrzeughalter Schaltgetriebe Benzin NaN 146 g/km (komb.) Kia False False False False False Stendal
12177 BMW 318 iA Lim. AHK|Glasdach|Navi|HUD|LED|17"LM|SHZ Schiebedach, Einparkhilfe Kamera, Anhängerkupp... € 33.699,-Sehr guter Preis False - - • DE-46149 Oberhausen 12.103 km 01/2021 115 kW (156 PS) Gebraucht 1 Fahrzeughalter Automatik Benzin 6,3 l/100 km 142 g/km (komb.) BMW False True False True False Oberhausen
12178 Dacia Spring Business NaN € 22.985,-Keine Angabe False Ihr Team der BOB Automobile • DE-45141 Essen 3.551 km 07/2021 33 kW (45 PS) Gebraucht 1 Fahrzeughalter Automatik Elektro - (l/100 km) 0 g/km (komb.) Dacia NaN NaN False NaN NaN Essen

12179 rows × 22 columns

Entfernen unerwünschter Zeichen und Werte

Im nächsten Schritt wird das Dataframe um störende oder überflüssige Character bereinigt. Dazu gehören störende Satzzeichen, Währungen, Strings, etc.

Da in einzelnen Fällen zusätzlich optionale Leasingpreise noch hinter Kaufpreisen angezeigt werden, müssen zuerst alle Zeichen hinter dem ersten Kaufpreis entfernt werden. Dann werden in der nächsten Codezeile alle weiteren nicht numerischen Zeichen entfernt.

# Bereinigung der Spalte "Preis"
AutoDF['Preis'] = AutoDF['Preis'].replace('(,-).*', '',regex=True)
AutoDF['Preis'] = AutoDF['Preis'].str.replace(r'[^0-9]+', '')

Da der Monat der Erstzulassung voraussichtlich keine große Rolle spielt und die Analyse der Spalte Erstzulassung erschwert, wird dieser entfernt sowie alle übrigbleibenden nicht numerischen Zeichen.

# Bereinigung der Spalte "Erstzulassung"
AutoDF['Erstzulassung'] = AutoDF['Erstzulassung'].replace('.*/', '',regex=True)
AutoDF['Erstzulassung'] = AutoDF['Erstzulassung'].replace(r'[^0-9]+', '',regex=True)

Bei der PS-Angabe muss zuerst der Wert in kW entfernt werden, danach alle weiteren nicht numerischen Zeichen.

# Bereinigung der Spalte "PS"
AutoDF['PS'] = AutoDF['PS'].replace(['.*kW','\(','PS\)'], '',regex=True)
AutoDF['PS'] = AutoDF['PS'].replace(r'[^0-9]+', '',regex=True)

Die nachfolgenden Spalten enthalten im Datensatz noch Einheiten. Diese wurden bereits im Spaltentitel integriert (bspw. Emissionen_g_pro_km) und werden somit aus den Datensätzen entfernt, sodass nur noch numerische Werte verbleiben.

# Bereinigung weiterer Spalten
AutoDF['km'] = AutoDF['km'].replace(r'[^0-9]+', '',regex=True)
AutoDF['Fahrzeughalter'] = AutoDF['Fahrzeughalter'].replace(r'[^0-9]+', '',regex=True)
AutoDF['Verbrauch_l_pro_100km'] = AutoDF['Verbrauch_l_pro_100km'].replace(['\(l/100 km\)', 'l/100 km','\(komb.\)'], '',regex=True)
AutoDF['Emissionen_g_pro_km'] = AutoDF['Emissionen_g_pro_km'].replace(r'[^0-9]+', '',regex=True)

Bei der Bereinigung fehlender Werte tritt das Problem auf, dass bei den Attributen Verbrauch und Emissionen fehlende Werte bei Elektroautos = 0 (also kein Verbrauch in L pro 100km) bedeuten, bei nicht Elektroautos jedoch tatsächlich fehlende Werte.

Um die Daten daher auswertbar zu machen, werden zuerst Verbrauch und Emissionen bei allen Fahrzeugen auf “NaN” gesetzt, sollten diese in irgendeiner Form fehlen. Dann werden speziell für Elektroautos die Werte auf “0” gesetzt, da nur diese Verbrauch und Emissionen von “0” haben können.

# Alle fehlenden Werte bei Verbrauch und Emissionen werden durch "NaN" ersetzt
AutoDF['Verbrauch_l_pro_100km'] = AutoDF['Verbrauch_l_pro_100km'].replace(['-','','0'], np.NaN,regex=True)
AutoDF['Emissionen_g_pro_km'] = AutoDF['Emissionen_g_pro_km'].replace(['-','','0'], np.NaN,regex=True)

# da keine Angabe bei Verbrauch und Emissionen bei Elektroautos korrekt sein kann, wird der Wert wieder durch 0 ersetzt
AutoDF.loc[AutoDF.Kraftstoff == 'Elektro', 'Verbrauch_l_pro_100km'] = 0
AutoDF.loc[AutoDF.Kraftstoff == 'Elektro', 'Emissionen_g_pro_km'] = 0

Auch bei weiteren Spalten, bei denen die Angabe in der Anzeige wohl optional ist, müssen die fehlenden Werte durch “NaN” ersetzt werden um diese als fehlend bzw. “NULL” in der EDA zu erkennen.

# Weitere fehlende Werte werden durch "NaN" ersetzt
AutoDF['Fahrzeughalter'] = AutoDF['Fahrzeughalter'].replace(['-',''], np.NaN,regex=True)
AutoDF['Erstzulassung'] = AutoDF['Erstzulassung'].replace('', np.NaN,regex=True)
AutoDF['km'] = AutoDF['km'].replace('', np.NaN,regex=True)
AutoDF['PS'] = AutoDF['PS'].replace('', np.NaN,regex=True)
AutoDF.head()
Titel Version Untertitel Preis Leasing km Erstzulassung PS Zustand Fahrzeughalter Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km Marke Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem Stadt
0 Volkswagen Golf II GL Tuningfahrz. für Schrauber rostfrei viel... NaN 2950 False 139800 1990 90 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Volkswagen NaN NaN False NaN NaN Neumarkt
1 Renault Alpine A310 2.5 Turbo V6 NaN 23900 False 73000 1990 200 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Renault NaN NaN False NaN NaN LUGO
2 Mercedes-Benz 190 190E 2.5-16 NaN 31500 False 90000 1990 194 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Mercedes-Benz NaN NaN False NaN NaN MELIDE
3 BMW Z1 2,7 Ltr.+BBS+RESTAURIERT+HARDTOP NaN 73900 False 75156 1990 204 Gebraucht NaN Schaltgetriebe Benzin NaN NaN BMW NaN NaN False NaN NaN Osnabrück
4 Alfa Romeo Spider 2.0 Quadrifoglio Verde NaN 19900 False 79000 1990 128 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Alfa NaN NaN False NaN NaN VALENCIA

In der spalte Verbrauch_l_pro_100km wird das Komma zur Dezimaltrennung durch einen Punkt ersetzt, damit die Spalte als float definiert werden kann.

AutoDF['Verbrauch_l_pro_100km'] = AutoDF['Verbrauch_l_pro_100km'].replace(',', '.',regex=True)

Abgesehen von Klimaanlage entstehen “NaN” Values in den neu erzeugten Ausstattungsspalten wenn die Spalte Untertitel “NaN” ist, daher werden diese nun durch False ersetzt (Wir gehen davon aus, dass die Ausstattung nicht enthalten ist wenn sie nicht im Untertitel erwähnt ist).

AutoDF['Alufelgen'] = AutoDF['Alufelgen'].replace(np.NaN, False)
AutoDF['Sitzheizung'] = AutoDF['Sitzheizung'].replace(np.NaN, False)
AutoDF['Einparkhilfe'] = AutoDF['Einparkhilfe'].replace(np.NaN, False)
AutoDF['Navigationssystem'] = AutoDF['Navigationssystem'].replace(np.NaN, False)
AutoDF
Titel Version Untertitel Preis Leasing km Erstzulassung PS Zustand Fahrzeughalter Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km Marke Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem Stadt
0 Volkswagen Golf II GL Tuningfahrz. für Schrauber rostfrei viel... NaN 2950 False 139800 1990 90 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Volkswagen False False False False False Neumarkt
1 Renault Alpine A310 2.5 Turbo V6 NaN 23900 False 73000 1990 200 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Renault False False False False False LUGO
2 Mercedes-Benz 190 190E 2.5-16 NaN 31500 False 90000 1990 194 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Mercedes-Benz False False False False False MELIDE
3 BMW Z1 2,7 Ltr.+BBS+RESTAURIERT+HARDTOP NaN 73900 False 75156 1990 204 Gebraucht NaN Schaltgetriebe Benzin NaN NaN BMW False False False False False Osnabrück
4 Alfa Romeo Spider 2.0 Quadrifoglio Verde NaN 19900 False 79000 1990 128 Gebraucht NaN Schaltgetriebe Benzin NaN NaN Alfa False False False False False VALENCIA
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
12174 Audi A3 advanced 40TFSI S-line PDC SMARTINT Käsmann - die große Auswahl im Neckar-Odenwald... 37930 False 2653 2021 190 Gebraucht 1 Automatik Benzin NaN NaN Audi False False False False False Mosbach
12175 Volkswagen Golf VIII 1.5 TSI OPF -Active- 6-Gang Navi/ SHZ Standheizung, Navigationssystem, 3-Zonen-Klima... 25480 False 8400 2021 131 Gebraucht 1 Schaltgetriebe Benzin 5.1 117 Volkswagen False False True True True Flensburg
12176 Kia Ceed / cee'd Ceed Sportswagon Spirit Rückfahrkamera Ihr Kia und Mitsubishi Vertragshändler 24489 False 19700 2021 140 Gebraucht 1 Schaltgetriebe Benzin NaN 146 Kia False False False False False Stendal
12177 BMW 318 iA Lim. AHK|Glasdach|Navi|HUD|LED|17"LM|SHZ Schiebedach, Einparkhilfe Kamera, Anhängerkupp... 33699 False 12103 2021 156 Gebraucht 1 Automatik Benzin 6.3 142 BMW False True False True False Oberhausen
12178 Dacia Spring Business NaN 22985 False 3551 2021 45 Gebraucht 1 Automatik Elektro 0 0 Dacia False False False False False Essen

12179 rows × 21 columns

In der Spalte Leasing werden die Werte noch mit 0.0 für False und 1.0 für True ausgegeben. Dies wird in Boolean Werte geändert.

AutoDF['Leasing'] = AutoDF['Leasing'].replace(0.0, False)
AutoDF['Leasing'] = AutoDF['Leasing'].replace(1.0, True)

Entfernen von fehlenden oder nicht benötigten Werten

Mit der .info() Methode werden nun alle Spalten des Dataframes mit deren Datentypen angezeigt.

AutoDF.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 12179 entries, 0 to 12178
Data columns (total 21 columns):
 #   Column                 Non-Null Count  Dtype 
---  ------                 --------------  ----- 
 0   Titel                  12179 non-null  object
 1   Version                11900 non-null  object
 2   Untertitel             7238 non-null   object
 3   Preis                  12179 non-null  object
 4   Leasing                12179 non-null  bool  
 5   km                     12179 non-null  object
 6   Erstzulassung          12179 non-null  object
 7   PS                     12049 non-null  object
 8   Zustand                12179 non-null  object
 9   Fahrzeughalter         6218 non-null   object
 10  Getriebe               12179 non-null  object
 11  Kraftstoff             12179 non-null  object
 12  Verbrauch_l_pro_100km  8076 non-null   object
 13  Emissionen_g_pro_km    5059 non-null   object
 14  Marke                  12179 non-null  object
 15  Alufelgen              12179 non-null  bool  
 16  Sitzheizung            12179 non-null  bool  
 17  Klimaanlage            12179 non-null  bool  
 18  Einparkhilfe           12179 non-null  bool  
 19  Navigationssystem      12179 non-null  bool  
 20  Stadt                  10300 non-null  object
dtypes: bool(6), object(15)
memory usage: 1.6+ MB

Das Dataframe hat 22 Spalten. Davon haben die meisten den Datentyp object, obwohl es sich bei einigen davon um numerische Werte handelt. Dies muss noch geändert werden. Lediglich die Boolean Spalten wie beispielsweise Leasing wurden korrekt identifiziert.
Die meisten Spalten haben keine NULL Werte. Allerdings exisitieren auch Spalten, die sehr viele NULL-Werte aufweisen. Beispielsweise Emissionen_g_pro_km.
Nachfolgend werden die NULL-Werte in einer heatmap visuaisiert.

sns.set_theme(style="ticks", color_codes=True)

# Identifizieren der NULL Werte via Heatmap
sns.heatmap(AutoDF.isnull(), 
            yticklabels=False,
            cbar=False, 
            cmap='viridis');
../_images/Projekt_63_01.png

In der Heatmap ist zu erkennen, dass sehr viele NULL-Werte in den Spalten Untertitel, Fahrzeughalter, Verbrauch_l_pro_100km und Emissionen_g_pro_km exisitieren.
Nachfolgend werden hierfür nochmal die exakten Mengen ausgegeben:

print(AutoDF.isnull().sum())
Titel                       0
Version                   279
Untertitel               4941
Preis                       0
Leasing                     0
km                          0
Erstzulassung               0
PS                        130
Zustand                     0
Fahrzeughalter           5961
Getriebe                    0
Kraftstoff                  0
Verbrauch_l_pro_100km    4103
Emissionen_g_pro_km      7120
Marke                       0
Alufelgen                   0
Sitzheizung                 0
Klimaanlage                 0
Einparkhilfe                0
Navigationssystem           0
Stadt                    1879
dtype: int64

Die Features Verbrauch_l_pro_100km, Emissionen_g_pro_km, km (Kilometerstand) und PS sollen in unserem Use Case genauer untersucht werden. Daher sollen im Folgenden alle Zeilen mit NULL Values entfernt werden.
Das Feature Fahrzeughalter soll kompett entfernt werden, da dieses auch häufig nicht gepflegt wurde und auch nicht unbedingt aussagekräftig ist über den Zustand & Wert des Autos.

AutoDF = AutoDF[AutoDF['Verbrauch_l_pro_100km'].notna()]
AutoDF = AutoDF[AutoDF['Emissionen_g_pro_km'].notna()]
AutoDF = AutoDF[AutoDF['km'].notna()]
AutoDF = AutoDF[AutoDF['PS'].notna()]
print(AutoDF.isnull().sum())
Titel                       0
Version                    27
Untertitel                946
Preis                       0
Leasing                     0
km                          0
Erstzulassung               0
PS                          0
Zustand                     0
Fahrzeughalter           1418
Getriebe                    0
Kraftstoff                  0
Verbrauch_l_pro_100km       0
Emissionen_g_pro_km         0
Marke                       0
Alufelgen                   0
Sitzheizung                 0
Klimaanlage                 0
Einparkhilfe                0
Navigationssystem           0
Stadt                     379
dtype: int64

Als nächstes sollen alle Leasing Fahrzeuge aus dem DF entfernt werden. Diese waren nur als Werbung zwischen den eigentlichen Gebrauchtwagen Angeboten enthalten und verfälschen mit bspw. Preis (pro Monat als Leasing) die Statistiken.

# Prüfung ob Leasing Fahrzeuge enthalten sind (Leasing == True)
AutoDF["Leasing"].unique()
array([False,  True])
# DF wird neu erstellt nur mit Datensätzen die Leasing == False sind (~AutoDF.Leasing)
AutoDF = AutoDF[~AutoDF.Leasing] 
# Prüfung ob alle Leasing Fahrzeuge entfernt sind
AutoDF["Leasing"].unique()
array([False])

Die Spalte Leasing wird nun nicht mehr gebraucht und kann entfernt werden. Das gleiche gilt für die Spalte Zustand, da alle Fahrzeuge gebraucht sind und die Spalte Standort, da die benötigte Information Stadt bereits daraus abgezogen wurde.
Wie oben erklärt, soll auch die Spalte Fahrzeughalter entfernt werden.

# Prüfung ob wirklich nur gebrauchte Fahrzeuge enthalten sind
AutoDF["Zustand"].unique()
array(['Gebraucht'], dtype=object)
# Entfernen der beschriebenen Spalten
AutoDF = AutoDF.drop(columns=['Zustand','Leasing','Fahrzeughalter','Standort'])

Anpassung der Datentypen

Zunächst wird geprüft, welche Datentypen aktuell vorliegen

AutoDF.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 4570 entries, 7 to 12178
Data columns (total 18 columns):
 #   Column                 Non-Null Count  Dtype 
---  ------                 --------------  ----- 
 0   Titel                  4570 non-null   object
 1   Version                4543 non-null   object
 2   Untertitel             3627 non-null   object
 3   Preis                  4570 non-null   object
 4   km                     4570 non-null   object
 5   Erstzulassung          4570 non-null   object
 6   PS                     4570 non-null   object
 7   Getriebe               4570 non-null   object
 8   Kraftstoff             4570 non-null   object
 9   Verbrauch_l_pro_100km  4570 non-null   object
 10  Emissionen_g_pro_km    4570 non-null   object
 11  Marke                  4570 non-null   object
 12  Alufelgen              4570 non-null   bool  
 13  Sitzheizung            4570 non-null   bool  
 14  Klimaanlage            4570 non-null   bool  
 15  Einparkhilfe           4570 non-null   bool  
 16  Navigationssystem      4570 non-null   bool  
 17  Stadt                  4201 non-null   object
dtypes: bool(5), object(13)
memory usage: 522.2+ KB

Als nächstes werden die Datentypen angepasst, indem numerische Spalten einer Datentypkonvertierung unterzogen werden.

AutoDF['Preis'] = AutoDF['Preis'].astype('int')
AutoDF['km'] = AutoDF['km'].astype('int')
AutoDF['PS'] = AutoDF['PS'].astype('int')
AutoDF['Emissionen_g_pro_km'] = AutoDF['Emissionen_g_pro_km'].astype('int')
AutoDF['Erstzulassung'] = AutoDF['Erstzulassung'].astype('float')
AutoDF['Verbrauch_l_pro_100km'] = AutoDF['Verbrauch_l_pro_100km'].astype('float')

Die Spalten Getriebe, Kraftstoff Marke und Land weisen jeweils nur eine geringe Menge verschiedener Ausprägungen vor. Daher werden diese Spalten im Typ categorical abgespeichert. Alle übrigen Spalten verbleiben als object.

AutoDF['Getriebe'] = AutoDF['Getriebe'].astype('category')
AutoDF['Kraftstoff'] = AutoDF['Kraftstoff'].astype('category')
AutoDF['Marke'] = AutoDF['Marke'].astype('category')

Nun liegt das Dataframe in einer Form vor, in der die explorative Datenanalyse durchgeführt werden kann.

AutoDF.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 4570 entries, 7 to 12178
Data columns (total 18 columns):
 #   Column                 Non-Null Count  Dtype   
---  ------                 --------------  -----   
 0   Titel                  4570 non-null   object  
 1   Version                4543 non-null   object  
 2   Untertitel             3627 non-null   object  
 3   Preis                  4570 non-null   int32   
 4   km                     4570 non-null   int32   
 5   Erstzulassung          4570 non-null   float64 
 6   PS                     4570 non-null   int32   
 7   Getriebe               4570 non-null   category
 8   Kraftstoff             4570 non-null   category
 9   Verbrauch_l_pro_100km  4570 non-null   float64 
 10  Emissionen_g_pro_km    4570 non-null   int32   
 11  Marke                  4570 non-null   category
 12  Alufelgen              4570 non-null   bool    
 13  Sitzheizung            4570 non-null   bool    
 14  Klimaanlage            4570 non-null   bool    
 15  Einparkhilfe           4570 non-null   bool    
 16  Navigationssystem      4570 non-null   bool    
 17  Stadt                  4201 non-null   object  
dtypes: bool(5), category(3), float64(2), int32(4), object(4)
memory usage: 360.1+ KB

Die bereinigten Daten werden nochmal in einer neuen Tabelle in postgre SQL gesichert.

if not engine.has_table("autoscout24cars-cleaned"):
    AutoDF.to_sql(name='autoscout24cars-cleaned',index=True, index_label='index',con=engine)
else:
    print("table already exists")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [70], in <cell line: 1>()
----> 1 if not engine.has_table("autoscout24cars-cleaned"):
      2     AutoDF.to_sql(name='autoscout24cars-cleaned',index=True, index_label='index',con=engine)
      3 else:

NameError: name 'engine' is not defined

Deskriptive Statistik

Vorbereitung & allgemeine Untersuchung des DF

Nachfolgend werden alle numerischen Features in einer Liste gespeichert.

num_features=AutoDF.select_dtypes(include=np.number).columns.to_list()
num_features
['Preis',
 'km',
 'Erstzulassung',
 'PS',
 'Verbrauch_l_pro_100km',
 'Emissionen_g_pro_km']

Gleiches wird für alle nicht numerischen Features durchgeführt.

cat_features=AutoDF.select_dtypes(exclude=np.number).columns.to_list()
cat_features
['Titel',
 'Version',
 'Untertitel',
 'Getriebe',
 'Kraftstoff',
 'Marke',
 'Alufelgen',
 'Sitzheizung',
 'Klimaanlage',
 'Einparkhilfe',
 'Navigationssystem',
 'Stadt']

Für einen ersten Überblick über die Datenverteilung numerischer Features bietet sich die describe() Methode an. Diese gibt für jede Spalte die Anzahl, Durchschnitt, Standardabweichung, Minimum, Maximum sowie die Quartile an.

AutoDF.describe().transpose()
count mean std min 25% 50% 75% max
Preis 4570.0 22212.728228 24413.752948 1.0 7450.00 15999.0 27990.0 349900.0
km 4570.0 112090.814004 78410.721967 7.0 52954.25 98500.0 157000.0 729439.0
Erstzulassung 4570.0 2010.482932 7.251224 1990.0 2005.00 2011.0 2017.0 2021.0
PS 4570.0 196.174398 106.986416 5.0 125.00 165.0 239.0 772.0
Verbrauch_l_pro_100km 4570.0 7.351116 2.919708 0.0 5.50 6.7 8.8 61.0
Emissionen_g_pro_km 4570.0 177.867396 65.947109 0.0 135.00 163.0 213.0 595.0

Hier fällt bereits auf, dass teils extreme Werte vorliegen, wie bspw. max Werte beim Kilometerstand von 729.439km oder Fahrzeuge mit einem Verbrauch von 61 Liter auf 100km. Diese Werte fallen als extrem auf, da sie sehr stark vom vierten Quartil (75%) abweichen.
Der Durchschnittspreis der von uns Untersuchten Fahrzeuge liegt bei ca. 22.000€ und der durchschittliche Kilometerstand bei ca 112.000km

Nachfolgend wird die Anzahl der unique values je Feature ausgegeben.

for col in AutoDF.columns:
    values = AutoDF[col].unique()
    print(col, "has", len(AutoDF[col].unique()), "unique values")
Titel has 785 unique values
Version has 4311 unique values
Untertitel has 3372 unique values
Preis has 1817 unique values
km has 3334 unique values
Erstzulassung has 32 unique values
PS has 277 unique values
Getriebe has 4 unique values
Kraftstoff has 7 unique values
Verbrauch_l_pro_100km has 158 unique values
Emissionen_g_pro_km has 276 unique values
Marke has 63 unique values
Alufelgen has 2 unique values
Sitzheizung has 2 unique values
Klimaanlage has 2 unique values
Einparkhilfe has 2 unique values
Navigationssystem has 2 unique values
Stadt has 1067 unique values

Alle Features haben mindestens zwei verschiedene Ausprägungen. Variablen mit nur einem “unique value” würden keinen Mehrwert liefern für unsere Untersuchung.
Außerdem können wir bspw. erkennen, dass 63 verschiedene Automarken vertreten sind und die Fahrzeuge aus 1067 verschiedenen Städten angeboten werden und aus 32 unterschiedlichen Jahren stammen.

Als nächstes wird die Anzahl der Fahrzeuge pro Kraftstoffart ausgegeben.

print(AutoDF['Kraftstoff'].value_counts())
Benzin            3141
Diesel            1267
Elektro/Benzin      81
Elektro             38
Autogas (LPG)       21
Elektro/Diesel      21
Ethanol              1
Name: Kraftstoff, dtype: int64

Die häufigste Kraftstoffart ist Benzin, gefolgt von Diesel.
Alle anderen Kraftstoffarten kommen in Relation zur Gesamtmenge an Fahrzeugen eher selten vor.

Ebenso interessant ist die Anzahl der Fahrzeuge pro Getriebeart.

print(AutoDF['Getriebe'].value_counts())
Schaltgetriebe    2346
Automatik         2200
Halbautomatik       17
- (Getriebe)         7
Name: Getriebe, dtype: int64

Automatik und Schaltgetriebe kommen ungefähr gleich oft vor. Halbautomatikfahrzeuge kommen dagegen eher selten vor.
Außerdem fällt hier auf, dass 7 Datensätze keinen Wert haben für Getriebe - diese Datensätze werden als nächstes entfernt.

# Dataframe wird erstellt nur mit vorhandenen Werten bei "Getriebe"
AutoDF=AutoDF[AutoDF['Getriebe'].str.contains('- \(Getriebe\)')==False]
print(AutoDF['Getriebe'].value_counts())
Schaltgetriebe    2346
Automatik         2200
Halbautomatik       17
- (Getriebe)         0
Name: Getriebe, dtype: int64

Nachfolgend wird der prozentuale Anteil jeder Automarke in Bezug auf die Gesamtmasse aller Fahrzeuge ausgegeben.

print(AutoDF['Marke'].value_counts(normalize=True))
Audi             0.146833
BMW              0.135437
Mercedes-Benz    0.133684
Volkswagen       0.129520
Opel             0.050405
Ford             0.047556
Porsche          0.032435
Volvo            0.028490
MINI             0.021696
Skoda            0.020820
Peugeot          0.018190
Toyota           0.017532
SEAT             0.015779
Renault          0.015560
Mazda            0.015560
Jaguar           0.013807
Hyundai          0.012053
Alfa             0.011834
Kia              0.011615
Nissan           0.010958
Fiat             0.010739
Citroen          0.008766
Land             0.007890
Honda            0.007890
Cupra            0.007670
smart            0.005260
Mitsubishi       0.005041
Dacia            0.004821
Saab             0.004602
Aston            0.004602
Suzuki           0.004383
Jeep             0.003945
Maserati         0.003726
Chevrolet        0.003287
Lexus            0.003287
Dodge            0.002192
Ferrari          0.002192
Subaru           0.001972
Bentley          0.001972
SsangYong        0.001534
Abarth           0.001534
Daihatsu         0.001315
Alpina           0.001315
Chrysler         0.001096
HUMMER           0.001096
MG               0.000877
Lancia           0.000877
Sonstige         0.000877
Cadillac         0.000657
Daewoo           0.000657
Tesla            0.000438
DS               0.000438
Maybach          0.000438
Rover            0.000438
Infiniti         0.000438
Lamborghini      0.000438
Rolls-Royce      0.000219
Corvette         0.000219
Lada             0.000219
Polestar         0.000219
Caterham         0.000219
Aixam            0.000219
Lincoln          0.000219
Name: Marke, dtype: float64

Die häufigste in unserem Abzug vorkommende Automarke ist Audi, dicht gefolgt von von BMW und Mercedes-Benz.
Sehr selten vorkommende Automarken sind bspw. Lincoln, Aixam und Caterham.

Um allgemein noch einen guten Überblick über die Verteilung der numerischen Variablen zu bekommen, werden Histogramme erzeugt.

# Erstellen von Histogrammen der numerischen Variablen
AutoDF.hist(bins=20, figsize=(20,15))
plt.show("notebook")
../_images/Projekt_110_01.png

Für einzelne Variablen wie bspw. km oder PS lassen sich rechtsschiefe Verteilungen erkennen. Vor allem bei Erstzulassung lässt sich aber kein Schwerpunkt in der Vertilung erkennen, lediglich ein leichter Trend zu weniger alten Fahrzeugen.
Da vor allem der Preis für diese Untersuchung interessant ist, wird dieses Histogramm noch einmal detaillierter dargestellt.

#Erstellung eines detaillierten Histogramm zur Variable Preis
fig = px.histogram(AutoDF, x="Preis",title="Distribution over price (Euro)")
fig.show()

Korrelationsanalyse

Um eine erste Übersicht über mögliche Zusammenhänge zwischen den verschiedenen Variablen zu erhalten, wird zunächst ein Pairplot erstellt. Der Plot eignet sich besonders für numerische Variablen, durch farbliche Markierung kann allerdings auch eine kategoriale Variable dargestellt werden.

sns.pairplot(data=AutoDF, vars=["Preis","PS","km","Erstzulassung","Verbrauch_l_pro_100km","Emissionen_g_pro_km"],
             hue="Kraftstoff",)
<seaborn.axisgrid.PairGrid at 0x19fb0266280>
../_images/Projekt_115_11.png

Tatsächlich können im Pairplot Zusammenhänge zwischen einzelnen Variablen erkannt werden. Vor allem die starke Korrelation zwischen Verbrauch und Emissionen fällt im Plot auf, ist allerdings selbstverständlich da mit höherem Verbrauch in der Regel auch mehr Emissionen erzeugt werden. Doch auch weniger starke Abhängigkeiten können erkannt werden wie bspw. zwischen PS und Preis oder zwischen PS und Verbrauch.

Durch die farbliche Markierung der Kraftstoffart lässt sich hier auch schon gut erkennen, dass Benzin Fahrzeuge eher einen höheren Verbrauch und höhere Emissionen erzeugen als Diesel Fahrzeuge. Auch Autogas scheint tendentiell mehr Emissionen zu erzeugen als Diesel Fahrzeuge.

Nach der optischen Darstellung sollen nun im nächsten Schritt die Abhängigkeiten noch einmal in Zahlen dargestellt werden.

# Erstellen einer Korrelationsmatrix
corr_matrix = AutoDF.corr()
corr_matrix
Preis km Erstzulassung PS Verbrauch_l_pro_100km Emissionen_g_pro_km Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem
Preis 1.000000 -0.421786 0.375126 0.701054 0.234156 0.232313 -0.147829 0.002652 -0.089297 0.037044 0.041534
km -0.421786 1.000000 -0.568111 -0.127463 0.210769 0.255970 0.108523 -0.119208 -0.031420 -0.153058 -0.142417
Erstzulassung 0.375126 -0.568111 1.000000 0.117229 -0.496235 -0.506678 -0.055640 0.201850 0.068506 0.311526 0.315454
PS 0.701054 -0.127463 0.117229 1.000000 0.563397 0.579788 -0.141854 0.011839 -0.142537 0.015200 0.062163
Verbrauch_l_pro_100km 0.234156 0.210769 -0.496235 0.563397 1.000000 0.933004 -0.046658 -0.074132 -0.080756 -0.147325 -0.118214
Emissionen_g_pro_km 0.232313 0.255970 -0.506678 0.579788 0.933004 1.000000 -0.043258 -0.079571 -0.082951 -0.153974 -0.123025
Alufelgen -0.147829 0.108523 -0.055640 -0.141854 -0.046658 -0.043258 1.000000 0.055907 0.179344 0.042522 0.047007
Sitzheizung 0.002652 -0.119208 0.201850 0.011839 -0.074132 -0.079571 0.055907 1.000000 0.125800 0.203946 0.244613
Klimaanlage -0.089297 -0.031420 0.068506 -0.142537 -0.080756 -0.082951 0.179344 0.125800 1.000000 0.065015 0.099932
Einparkhilfe 0.037044 -0.153058 0.311526 0.015200 -0.147325 -0.153974 0.042522 0.203946 0.065015 1.000000 0.320108
Navigationssystem 0.041534 -0.142417 0.315454 0.062163 -0.118214 -0.123025 0.047007 0.244613 0.099932 0.320108 1.000000
# Erstellen einer Heatmap um Abhängigkeiten zwischen den verschiedenen Variablen zu visualisieren

# Einstellung um nur den relevanten Teil der Matrix zu plotten
mask = np.zeros_like(corr_matrix)
mask[np.triu_indices_from(mask)]= True

# Erstellen der Heatmap
plt.subplots(figsize=(11, 15))
heatmap = sns.heatmap(corr_matrix, 
                      mask = mask, 
                      square = True, 
                      linewidths = .5,
                      cmap = 'coolwarm',
                      cbar_kws = {'shrink': .6,
                                'ticks' : [-1, -.5, 0, 0.5, 1]},
                      vmin = -1,
                      vmax = 1,
                      annot = True,
                      annot_kws = {"size": 10})
../_images/Projekt_118_01.png

Durch die Korrelationsmatrix sowie deren Visualisierung mit einer Heatmap können Korrelationen zwischen den verschiedenen Variablen auf einen Blick erkannt werden. Die farblich besonders saturierten Feleder (dunkelblau und dunkelrot) weisen auf besonders starke Abhängigkeit hin.

Da die offensichtlichsten Korrelationen (bspw. zwischen PS, Verbrauch und Emissionen) naheliegend und daher nicht besonders interessant sind, soll im Folgenden näher untersucht werden, welche Faktoren einen besonderen Einfluss auf den Preis haben.

# Berechnung der Korrelationen der einzelnen Variablen zur Variable "Preis"
corr = AutoDF.corr()
corr['Preis'].sort_values(ascending=False)
Preis                    1.000000
PS                       0.701054
Erstzulassung            0.375126
Verbrauch_l_pro_100km    0.234156
Emissionen_g_pro_km      0.232313
Navigationssystem        0.041534
Einparkhilfe             0.037044
Sitzheizung              0.002652
Klimaanlage             -0.089297
Alufelgen               -0.147829
km                      -0.421786
Name: Preis, dtype: float64

Von den numerischen Variablen haben PS, km und Erstzulassung den höchsten Einfluss auf den Preis. Diese Variablen sollen daher näher untersucht werden. Dazu wird ein lmplot genutzt:

# Plot mit Trendlinie für PS & Preis
sns.lmplot(x='PS', y='Preis', data=AutoDF, 
line_kws={'color': 'darkred'}, ci=False);
../_images/Projekt_122_01.png
# Plot mit Trendlinie für km & Preis
sns.lmplot(x='km', y='Preis', data=AutoDF, 
line_kws={'color': 'darkred'}, ci=False);
../_images/Projekt_123_01.png
# Plot mit Trendlinie für Erstzulassung & Preis
sns.lmplot(x='Erstzulassung', y='Preis', data=AutoDF, 
line_kws={'color': 'darkred'}, ci=False)
<seaborn.axisgrid.FacetGrid at 0x19fb91510d0>
../_images/Projekt_124_11.png

Zusätzlich wird hier noch ein Boxplot erstellt, an welchem die Verteilung sowie Ausreißer besser erkannt werden können. Hier fällt auf, dass Ausreißer vor allem Luxusmarken wie Lamborghini, Bentley oder Porsche sind oder sehr teuere Modelle von bspw. BMW (BMW Z8).

# Erstellung eines Boxplots zur Preis & Erstzulassung
fig = px.box(data_frame=AutoDF,x="Erstzulassung", y="Preis",
                 hover_name="Titel")
fig.show()

Wie auch zuvor schon vermutet lässt sich hier nochmal klar bestätigen (durchschnittlich):
1. Mit steigenden PS steigt auch der Preis
2. Mit steigendem Kilometerstand sinkt der Preis
3. Mit steigendem Jahr der Erstzulassung steigt auch der Preis

Marke & Preis

Mit Hilfe eines Boxplots wollen wir außerdem untersuchen, wie sich die Preisverteilung bei den unterschiedlichen Automarken verhält. Um das besonders übersichtlich zu gestalten, wird die Anzeige aufsteigend nach dem Durchschnittspreis je Marke sortiert:

#sortieren der Marken nach Durchschnittspreis
sorted_nb = AutoDF.groupby(['Marke'])['Preis'].median().sort_values()
#sorted_nb
#Anpassen der seaborn Plotgröße um den Plot übersichtlich darzustellen
sns.set(rc={'figure.figsize':(15,15)})
#Erzeugen des Plots
sns.boxplot(x=AutoDF['Preis'], y=AutoDF['Marke'], order=list(sorted_nb.index),orient="h")
<AxesSubplot:xlabel='Preis', ylabel='Marke'>
../_images/Projekt_131_11.png

Am Plot mit Sortierung lässt sich gut erkennen, dass Luxus-Automarken wie Lamborghini, Aston Martin und Rolls-Royce auch bei gebrauchten Fahrzeugen im Schnitt die teuersten Angebote darstellen. Vor allem bei Lamborghini und Aston Martin ist fällt auch, dass die Preisspanne innerhalb der vier Quartile des Boxplots sehr groß ist.
Günstige Fahrzeuge werden von den Marken Daewoo, Daihatsu und Rover angeboten. Am Beispiel Rover können wir erkennen, dass auf Grund einer geringen Auswahl an Fahrzeugangeboten mit einem sehr hohen Kilometerstand schnell ein sehr geringer durschnittlicher Preis entstehen kann:

AutoDF.loc[AutoDF['Marke']=='Rover']
Titel Version Untertitel Preis km Erstzulassung PS Getriebe Kraftstoff Verbrauch_l_pro_100km Emissionen_g_pro_km Marke Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem Stadt
3558 Rover 75 2.0 V6 Club Leer Memory Clima . NaN 1650 303358 1999.0 150 Schaltgetriebe Benzin 9.4 232 Rover False False False False False APELDOORN
3903 Rover 75 2.5 V6 Sterling 177Pk Automaat (6 CILINDER, CL... NaN 4945 157651 2000.0 177 Automatik Benzin 11.6 286 Rover False False False False False IJssel

Ausstattung & Preis

Als nächstes soll untersucht werden, inwiefern die Ausstattungsmerkmale einen eindeutigen Einfluss auf den Preis haben.

Austtattung=['Klimaanlage','Alufelgen','Sitzheizung','Einparkhilfe','Navigationssystem']
Austtattung
['Klimaanlage',
 'Alufelgen',
 'Sitzheizung',
 'Einparkhilfe',
 'Navigationssystem']
fig, ax = plt.subplots(2, 3, figsize=(15, 10))
for var, subplot in zip(Austtattung, ax.flatten()):
    sns.boxplot(x=var, y='Preis', data=AutoDF, ax=subplot)
../_images/Projekt_137_01.png

Fazit: durch die Boxplots lässt sich leider kaum eine Auswirkung der Ausstattungsmerkmale auf den Preis erkennen. Vermutlich ist die Beschreibung der Ausstattung oft nicht detailliert gepflegt. Man kann vermuten, dass vor allem bei modernen und teuren Autos eine Klimaanlage bspw. selbstverständlich ist und daher nicht extra im Untertitel der Anzeige erwähnt wird.

Kraftstoff & Preis

# Gruppieren des DF nach Kraftstoff mit durschnittlichem Preis
PriceAveragePerKraftstoff=AutoDF.groupby(by="Kraftstoff")["Preis"].mean()
# Erzeugen des Bar Plots mit den zuvor erzeugten Gruppierungen
PriceAveragePerKraftstoff.plot(kind="bar",figsize=(12,6),color="m",title="Durschnittlicher Preis nach Kraftstoff")
<AxesSubplot:title={'center':'Durschnittlicher Preis nach Kraftstoff'}, xlabel='Kraftstoff'>
../_images/Projekt_140_11.png

Am Balkendiagramm können wir erkennen, dass Elektrofahrzeuge und Hybride im Durschnitt am teuersten gehandelt werden, Fahrzeuge mit Ethanol oder Autogas als Kraftstoff am günstigsten.
Da bei diesen Untersuchungen allerdings auch die Anzahl an vorliegenden Datensätzen eine Rolle spielt, wird diese noch einmal zusätzlich in einem weiteren Plot visualisiert:

#Anpassen der seaborn Plotgröße um den Plot übersichtlich darzustellen
sns.set(rc={'figure.figsize':(12,8)})
sns.stripplot(data=AutoDF, x="Kraftstoff", y="Preis" , size=3 )
<AxesSubplot:xlabel='Kraftstoff', ylabel='Preis'>
../_images/Projekt_142_11.png

Hier können wir erkennen, dass nur für Benzin und Diesel sehr viele Datensätze vorliegen - vor allem Ethanol scheint nur durch einen Datensatz vertreten zu sein und sollte daher mit Vorsicht interpretiert werden.

fig=px.scatter(AutoDF,x="PS",y="Preis",color="Emissionen_g_pro_km",size="Verbrauch_l_pro_100km",
              hover_data=["Marke","Titel","Kraftstoff"],title="Price over PS",
              trendline="ols")
fig.show()

Getriebe & Preis

Als nächstes wird der Einfluss der Art des Getriebes auf den Preis untersucht:

# Erstellen eines erweiterten Boxplots zu Getriebe & Preis
sns.boxenplot(data=AutoDF, x="Getriebe", y="Preis");
../_images/Projekt_147_01.png

Am Boxplot können wir erkennen, das Fahrzeuge mit Schaltgetriebe im Durchschnitt am günstigsten verkauft werden, Fahrzeuge mit Automatik im Durchschnitt am teuersten.
Das kann noch etwas detaillierter dargstellt werden:

#Erstellen einzelner DF nach Getriebe für folgende Visualisierung
Automatik=AutoDF[AutoDF["Getriebe"]=="Automatik"]
Schaltgetriebe=AutoDF[AutoDF["Getriebe"]=="Schaltgetriebe"]
Halbautomatik=AutoDF[AutoDF["Getriebe"]=="Halbautomatik"]
#Erstellen von Series Objekten mit den entsprechenden Preisen
npAutomatik = Automatik["Preis"]
npSchaltgetriebe = Schaltgetriebe["Preis"]
npHalbautomatik = Halbautomatik["Preis"]
# Zusammenfassen der Series Objekte zu einer Liste
data = [npAutomatik.values, npSchaltgetriebe.values, npHalbautomatik.values]

# Parameter für Plot
group_labels = ['Automatik', 'Schaltgetriebe', 'Halbautomatik']
colors = ['#462EDE', '#DE2EBE', '#FF8033']

# Erstellen des Plots mit zuvor erzeugter Liste und Parametern
fig = ff.create_distplot(data, group_labels, 
                         bin_size=3000, show_rug=False)

# Anpassung Titetl
fig.update_layout(title_text='Preisverteilung nach Getriebe')
fig.show()

Untersuchung von Unterschieden zwischen Automarken

Neben den zuvor schon festgestellten Unterschieden im Preis sollen nun auch noch weitere Unterschiede zwischen den Automarken untersucht werden. Dazu werden die numerischen Variablen der Marken zunächst nach dem Durchschnitt gruppiert.

MarkenGruppiert=AutoDF.groupby(by="Marke").mean()
MarkenGruppiert
Preis km Erstzulassung PS Verbrauch_l_pro_100km Emissionen_g_pro_km Alufelgen Sitzheizung Klimaanlage Einparkhilfe Navigationssystem
Marke
Abarth 18979.142857 34271.285714 2017.142857 162.571429 6.585714 155.714286 0.000000 0.000000 0.571429 0.142857 0.285714
Aixam 7950.000000 11149.000000 2010.000000 8.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
Alfa 16468.685185 108397.574074 2006.777778 184.629630 8.590741 205.333333 0.166667 0.018519 0.185185 0.074074 0.055556
Alpina 33811.333333 130511.000000 2010.500000 366.333333 8.533333 208.333333 0.000000 0.333333 0.000000 0.333333 0.333333
Aston 112095.238095 44050.190476 2008.952381 492.619048 15.819048 373.095238 0.000000 0.142857 0.142857 0.000000 0.047619
Audi 25795.776119 110393.247761 2012.222388 224.489552 6.992239 170.143284 0.126866 0.256716 0.292537 0.240299 0.313433
BMW 21189.422330 132068.305825 2007.970874 216.035599 7.697735 186.805825 0.156958 0.257282 0.176375 0.265372 0.322006
Bentley 73719.333333 93012.666667 2007.111111 536.444444 16.922222 395.333333 0.111111 0.111111 0.111111 0.222222 0.222222
Cadillac 13830.000000 140875.000000 2011.000000 264.000000 12.433333 289.333333 0.000000 0.000000 0.000000 0.000000 0.000000
Caterham 27999.000000 22900.000000 2000.000000 135.000000 8.300000 164.000000 0.000000 0.000000 0.000000 0.000000 0.000000
Chevrolet 12699.133333 88319.800000 2011.133333 171.866667 7.793333 186.066667 0.066667 0.400000 0.133333 0.200000 0.200000
Chrysler 10049.800000 146629.600000 2004.800000 241.200000 10.100000 234.800000 0.400000 0.000000 0.200000 0.200000 0.000000
Citroen 7289.725000 140696.250000 2009.200000 119.725000 6.122500 151.075000 0.125000 0.100000 0.275000 0.175000 0.125000
Corvette 49500.000000 71000.000000 2014.000000 466.000000 12.300000 282.000000 0.000000 1.000000 1.000000 0.000000 0.000000
Cupra 35797.257143 23084.542857 2020.257143 246.857143 6.094286 160.657143 0.057143 0.485714 0.285714 0.485714 0.628571
DS 17240.000000 52900.000000 2016.500000 155.500000 5.700000 133.000000 0.000000 1.000000 0.500000 0.000000 1.000000
Dacia 20206.818182 24118.045455 2019.090909 59.818182 1.618182 37.636364 0.000000 0.000000 0.045455 0.000000 0.000000
Daewoo 2354.666667 81918.666667 2001.000000 73.666667 7.166667 173.333333 0.000000 0.000000 0.333333 0.000000 0.000000
Daihatsu 3136.833333 128231.500000 2004.333333 72.000000 5.250000 124.333333 0.500000 0.000000 0.333333 0.166667 0.000000
Dodge 27937.900000 102139.900000 2008.900000 305.800000 12.680000 302.400000 0.200000 0.100000 0.100000 0.100000 0.200000
Ferrari 107657.000000 41797.400000 2005.400000 481.300000 17.550000 372.900000 0.100000 0.000000 0.100000 0.000000 0.000000
Fiat 7768.265306 113770.163265 2007.020408 106.551020 6.530612 153.408163 0.326531 0.061224 0.285714 0.122449 0.102041
Ford 16488.728111 86040.525346 2013.313364 143.092166 6.213825 151.198157 0.304147 0.170507 0.299539 0.308756 0.262673
HUMMER 30887.800000 141125.400000 2005.400000 337.000000 16.820000 396.800000 0.200000 0.200000 0.200000 0.200000 0.000000
Honda 13044.277778 108555.638889 2009.805556 162.583333 7.302778 171.750000 0.277778 0.138889 0.305556 0.277778 0.166667
Hyundai 13648.000000 91240.472727 2013.109091 158.690909 7.152727 170.127273 0.145455 0.290909 0.363636 0.181818 0.254545
Infiniti 19890.000000 132210.000000 2013.500000 265.500000 9.050000 210.500000 0.000000 0.000000 0.000000 0.000000 0.000000
Jaguar 32026.746032 104132.015873 2007.158730 329.238095 10.461905 252.000000 0.158730 0.158730 0.269841 0.174603 0.285714
Jeep 17115.277778 122080.388889 2008.388889 176.555556 9.961111 242.166667 0.166667 0.277778 0.222222 0.166667 0.333333
Kia 18223.358491 75401.566038 2015.433962 155.433962 6.369811 157.301887 0.169811 0.415094 0.264151 0.320755 0.377358
Lada 13445.000000 11500.000000 2018.000000 106.000000 6.200000 141.000000 0.000000 0.000000 0.000000 0.000000 0.000000
Lamborghini 262400.000000 38195.000000 2007.000000 570.500000 20.450000 460.000000 0.000000 0.000000 0.000000 0.000000 0.000000
Lancia 4420.000000 134231.000000 2005.750000 152.000000 9.450000 224.250000 0.000000 0.000000 0.000000 0.250000 0.000000
Land 30803.250000 155212.055556 2011.250000 255.972222 10.391667 260.805556 0.055556 0.222222 0.083333 0.305556 0.305556
Lexus 13204.400000 168577.866667 2005.666667 239.466667 9.720000 230.400000 0.000000 0.266667 0.133333 0.200000 0.266667
Lincoln 5950.000000 259841.000000 1999.000000 204.000000 11.900000 283.000000 0.000000 0.000000 0.000000 0.000000 0.000000
MG 9210.000000 64014.250000 1999.500000 125.250000 7.550000 181.250000 0.500000 0.000000 0.000000 0.000000 0.000000
MINI 13069.808081 106059.323232 2010.444444 136.181818 6.120202 146.202020 0.212121 0.323232 0.343434 0.242424 0.242424
Maserati 56921.470588 87515.764706 2009.647059 397.529412 14.223529 337.000000 0.117647 0.352941 0.176471 0.176471 0.235294
Maybach 74994.500000 124500.000000 2009.500000 540.500000 13.800000 328.500000 0.000000 0.000000 0.000000 0.000000 0.000000
Mazda 12035.676056 109152.661972 2010.577465 135.619718 6.749296 164.760563 0.323944 0.239437 0.323944 0.169014 0.197183
Mercedes-Benz 29056.142623 112974.018033 2010.145902 244.031148 8.284590 199.239344 0.126230 0.288525 0.209836 0.296721 0.304918
Mitsubishi 8879.869565 127835.608696 2007.434783 134.608696 6.765217 168.913043 0.260870 0.130435 0.347826 0.130435 0.086957
Nissan 9995.260000 112635.000000 2009.660000 134.500000 6.922000 168.240000 0.140000 0.200000 0.340000 0.220000 0.260000
Opel 12099.721739 98926.069565 2011.569565 138.086957 6.315652 153.156522 0.234783 0.234783 0.408696 0.234783 0.247826
Peugeot 8839.566265 126257.445783 2008.602410 126.662651 6.415663 154.650602 0.180723 0.180723 0.289157 0.108434 0.132530
Polestar 69900.000000 8200.000000 2021.000000 476.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
Porsche 57766.459459 97910.763514 2006.675676 342.918919 10.845270 258.182432 0.108108 0.250000 0.277027 0.155405 0.216216
Renault 12700.661972 124671.661972 2006.619718 130.971831 7.236620 167.943662 0.295775 0.070423 0.309859 0.126761 0.239437
Rolls-Royce 119900.000000 104000.000000 2005.000000 460.000000 15.900000 385.000000 0.000000 0.000000 0.000000 0.000000 0.000000
Rover 3297.500000 230504.500000 1999.500000 163.500000 10.500000 259.000000 0.000000 0.000000 0.000000 0.000000 0.000000
SEAT 17321.236111 84424.777778 2014.902778 155.069444 5.673611 138.263889 0.250000 0.541667 0.180556 0.486111 0.486111
Saab 7827.428571 181520.333333 2002.904762 181.000000 9.114286 218.000000 0.333333 0.238095 0.380952 0.142857 0.142857
Skoda 18458.947368 89110.115789 2015.410526 142.378947 5.589474 136.473684 0.189474 0.494737 0.389474 0.357895 0.263158
Sonstige 17442.500000 137740.250000 2003.750000 101.000000 3.225000 85.500000 0.000000 0.000000 0.000000 0.000000 0.000000
SsangYong 11278.571429 104561.000000 2013.285714 159.142857 6.514286 172.142857 0.000000 0.571429 0.285714 0.571429 0.142857
Subaru 12847.444444 165414.888889 2008.666667 170.888889 8.511111 203.666667 0.222222 0.222222 0.333333 0.000000 0.333333
Suzuki 5576.100000 137590.800000 2004.600000 99.800000 7.060000 158.800000 0.300000 0.100000 0.350000 0.100000 0.050000
Tesla 99563.000000 33300.000000 2015.000000 400.500000 0.000000 0.000000 0.000000 0.000000 0.000000 0.500000 0.000000
Toyota 12268.900000 121399.000000 2008.800000 132.400000 6.548750 155.075000 0.162500 0.137500 0.400000 0.125000 0.125000
Volkswagen 17188.986464 115568.905245 2011.702200 146.967851 6.291709 155.333333 0.219966 0.291032 0.384095 0.323181 0.380711
Volvo 19385.423077 154742.623077 2010.223077 205.007692 7.077692 173.261538 0.200000 0.315385 0.323077 0.169231 0.200000
smart 6064.958333 88368.583333 2004.750000 70.250000 4.862500 115.416667 0.458333 0.125000 0.375000 0.000000 0.000000
# Erstellen eines Barcharts zum durchschnittlichen Verbrauch pro Marke
fig=px.bar(x=MarkenGruppiert.index,y=MarkenGruppiert["Verbrauch_l_pro_100km"],title="Durchschnittlicher Verbrauch pro Automarke (Liter/100km)")
fig.show()

Hier fällt auf, dass Luxus- & Sportmarken wie bspw. Ferrari, Lamborghini und Bentley im Schnitt den höchsten Verbrauch haben. Reine Elektromarken wie Polestar oder Tesla haben logischerweise einen durchschnittlichen Verbrauch von 0.

Als nächstes soll die Eigenschaft PS untersucht werden auf Unterschiede zwischen den Automarken.

#sortieren der Marken nach Durchschnitts PS
sorted_ps = AutoDF.groupby(['Marke'])['PS'].median().sort_values()
#Anpassen der Größe des Plots
sns.set(rc={'figure.figsize':(15,15)})
#Erzeugen des Plots
sns.boxplot(x=AutoDF['PS'], y=AutoDF['Marke'], order=list(sorted_ps.index),orient="h")
<AxesSubplot:xlabel='PS', ylabel='Marke'>
../_images/Projekt_157_11.png

Ähnlich wie beim Verbrauch fallen auch hier bei den PS Luxusmarken wie Lamborghini & Bentley auf. Interessant ist hier jedoch, das auch Maybach sehr weit vorne dabei ist, zuvor jedoch nicht beim Verbrauch in den Top 3 gelandet ist (lediglich Platz 9 wie in Liste unten zu sehen ist). Maybach scheint daher eher effiziente Motoren zu verwenden.

sorted_verbrauch = AutoDF.groupby(['Marke'])['Verbrauch_l_pro_100km'].median().sort_values()
sorted_verbrauch
Marke
Aixam             0.00
Dacia             0.00
Tesla             0.00
Polestar          0.00
Sonstige          2.40
smart             4.90
Daihatsu          4.90
SEAT              5.40
Skoda             5.40
Cupra             5.50
DS                5.70
Ford              6.00
Kia               6.10
MINI              6.10
Volkswagen        6.10
SsangYong         6.20
Opel              6.20
Lada              6.20
Citroen           6.20
Peugeot           6.40
Audi              6.40
Nissan            6.40
Fiat              6.40
Suzuki            6.40
Mitsubishi        6.60
Chevrolet         6.60
Toyota            6.65
Mazda             6.70
Volvo             6.75
Abarth            6.80
Renault           7.00
Daewoo            7.00
Hyundai           7.20
Honda             7.20
Mercedes-Benz     7.50
MG                7.50
BMW               7.55
Chrysler          7.80
Caterham          8.30
Subaru            8.60
Alpina            8.70
Alfa              8.75
Lancia            8.80
Infiniti          9.05
Jeep              9.10
Saab              9.40
Land              9.50
Lexus             9.80
Rover            10.50
Porsche          11.30
Dodge            11.60
Lincoln          11.90
Jaguar           11.90
Corvette         12.30
Maybach          13.80
Cadillac         14.70
Maserati         14.70
Rolls-Royce      15.90
Aston            16.40
Bentley          17.10
HUMMER           18.10
Ferrari          18.60
Lamborghini      20.45
Name: Verbrauch_l_pro_100km, dtype: float64

Kartenvisualisierung

Als nächstes werden wird der Standort der zum Verkauf angebotenen Fahrzeuge in einer Landkarte visualisiert.
Für diese Zwecke wurde beim Webcrawling das Attribut Location von Autoscout24 abgezogen und anschließend in der Datenaufbereitung der Stadtname und das Land als extra Spalte angelegt.
Mithilfe des Geolocators werden jeder Stadt Longitude und Latitude zugeordnet und im Dataframe geoDF gespeichert. Dieses Dataframe wird anschließend über den Index mit dem AutoDF gejoined.

Aufgrund der Datenmenge wird für die Kartenvisualisierung nur ein Ausschnitt von 100 Datenpunkte verwendet. Diese werden in AutoDFsmall gespeichert.

AutoDFsmall = AutoDF[0:100]
geolocator = Nominatim(user_agent="my_app")
geoDF = pd.DataFrame()
for city in AutoDFsmall.index:
    try:
        location = geolocator.geocode(AutoDFsmall['Stadt'][city])
        geoDF = geoDF.append({"longitude": location.longitude, "latitude": location.latitude}, ignore_index=True)  
    except:
        geoDF = geoDF.append({"longitude": None, "latitude": None}, ignore_index=True)
geoDF
longitude latitude
0 13.322287 52.457257
1 9.278869 45.639544
2 11.048029 46.314475
3 8.816553 50.520168
4 8.764870 51.718960
5 7.136807 51.594201
6 11.280749 50.893925
7 11.280749 50.893925
8 7.340479 51.320742
9 9.278869 45.639544
10 11.048029 46.314475
11 10.992412 45.438496
12 11.048029 46.314475
13 8.241656 50.082038
14 13.738144 51.049329
15 11.048029 46.314475
16 6.759562 51.434999
17 11.280749 50.893925
18 10.037038 45.220864
19 4.746916 45.710702
20 11.048029 46.314475
21 11.048029 46.314475
22 9.149364 49.974054
23 11.048029 46.314475
24 11.048029 46.314475
25 4.654599 50.462995
26 16.786484 48.069744
27 6.229550 50.770291
28 -100.445882 39.783730
29 6.601410 51.545898
30 7.989620 50.959863
31 7.529418 49.774820
32 11.904747 48.732142
33 9.964009 53.886745
34 7.219664 51.481811
35 8.818337 53.434064
36 13.474937 54.353479
37 11.048029 46.314475
38 9.951305 52.152164
39 11.048029 46.314475
40 10.992412 45.438496
41 11.048029 46.314475
42 7.219985 51.538039
43 14.117785 51.041750
44 16.288722 47.107142
45 10.834281 47.768675
46 11.048029 46.314475
47 11.048029 46.314475
48 11.048029 46.314475
49 11.048029 46.314475
50 11.048029 46.314475
51 7.733330 50.466700
52 7.661086 47.714697
53 11.048029 46.314475
54 15.969177 47.280937
55 11.048029 46.314475
56 11.028736 50.977797
57 7.660722 47.612090
58 8.651177 49.872775
59 11.048029 46.314475
60 8.241656 50.082038
61 11.048029 46.314475
62 10.160149 48.304488
63 12.231224 51.846592
64 8.344097 49.998629
65 8.531007 52.019101
66 7.465279 51.514227
67 10.000654 53.550341
68 8.754158 45.839713
69 12.482932 41.893320
70 8.124701 52.302900
71 6.555575 50.541002
72 7.849400 47.996090
73 11.048029 46.314475
74 6.435364 51.194698
75 13.791598 48.108609
76 12.540308 51.347271
77 11.048029 46.314475
78 13.046481 47.798135
79 6.849350 51.297326
80 11.048029 46.314475
81 11.048029 46.314475
82 11.048029 46.314475
83 11.048029 46.314475
84 11.048029 46.314475
85 10.000654 53.550341
86 11.048029 46.314475
87 6.787122 50.661262
88 11.048029 46.314475
89 11.048029 46.314475
90 11.048029 46.314475
91 13.769831 47.933996
92 11.048029 46.314475
93 7.372762 47.598553
94 12.006399 49.058276
95 13.523585 52.484340
96 11.048029 46.314475
97 11.048029 46.314475
98 11.048029 46.314475
99 7.819339 49.141945
#Index von AutoDF zurücksetzen, da aufgrund der Entfernung der Null-Values bei Verbrauch und Emissionen viele Zeilen weggefallen sind
#sonst kann nicht mit GeoDF gejoined werden
AutoDFsmall = AutoDFsmall.reset_index(drop=True)

#Join von GeoDF und AutoDF über Index
AutoDFsmall = pd.merge(AutoDFsmall, geoDF, left_index=True, right_index=True)

Unter Verwendung von Longitude und Latitude werden die Fahrzeugstandorte auf einer Folium Map visualisiert.
Zusätzlich wird jedem Datenpunkt eine Pop-Up Beschreibung hingefügt, welche Stadtname, Autobeschreibung und Preis beinhaltet.

m = folium.Map([50.0 , 10.0],zoom_start=4)
for i in AutoDFsmall.index:
    try:
        folium.Marker( location=[ AutoDFsmall['latitude'][i], AutoDFsmall['longitude'][i] ], popup = [AutoDFsmall['Stadt'][i], AutoDFsmall['Titel'][i], AutoDFsmall['Preis'][i]]).add_to(m)
    except:
        continue
m
Make this Notebook Trusted to load map: File -> Trust Notebook

Regression

In diesem Kapitel wird ein einfaches Regressionsmodell zur Bestimmmung des Fahrzeugpreises auf Basis ausgewählter Features berechnet.
Hierfür wird die lineare Regression gewählt. Da die Regression nicht Fokus des Projekts ist, wurde auf einen Split der Daten in Trainings- und Testdaten verzichtet.
Zudem wird lediglich ein Modell berechnet und somit auf den Vergleich verschiedener Modell mit anschließender Auswahl des besten Modells verzichtet.
Für das Modell wurden jene Features ausgewählt, die in der vorherigen explorativen Datenanalyse die größte Korrelation zu Preis aufweisen.
Dazu gehören folgende Features:

  • PS

  • km

  • Erstzulassung

  • Kraftstoff

  • Verbrauch_l_pro_100km

  • Emissionen_g_pro_km

Da die Features Verbrauch und Emissionen eine sehr hohe Korrelation untereinander aufweisen, soll nur eines der beiden Features verwendet werden. Da die Abhängigkeit von Preis zu Verbrauch minimal höher ist als zu Emissionen, wird der Verbrauch gewählt.

# Fit Model
lm = smf.ols(formula='Preis ~ PS + km + Kraftstoff + Erstzulassung + Verbrauch_l_pro_100km', data=AutoDF).fit()
# Full summary
lm.summary()
OLS Regression Results
Dep. Variable: Preis R-squared: 0.634
Model: OLS Adj. R-squared: 0.633
Method: Least Squares F-statistic: 787.4
Date: Tue, 13 Sep 2022 Prob (F-statistic): 0.00
Time: 19:56:18 Log-Likelihood: -50282.
No. Observations: 4563 AIC: 1.006e+05
Df Residuals: 4552 BIC: 1.007e+05
Df Model: 10
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept -9.719e+05 9.9e+04 -9.814 0.000 -1.17e+06 -7.78e+05
Kraftstoff[T.Benzin] -580.8589 3276.059 -0.177 0.859 -7003.525 5841.807
Kraftstoff[T.Diesel] 5084.3293 3309.632 1.536 0.125 -1404.155 1.16e+04
Kraftstoff[T.Elektro] 2.346e+04 4266.256 5.500 0.000 1.51e+04 3.18e+04
Kraftstoff[T.Elektro/Benzin] 5039.1362 3770.621 1.336 0.181 -2353.111 1.24e+04
Kraftstoff[T.Elektro/Diesel] 7925.3502 4632.848 1.711 0.087 -1157.281 1.7e+04
Kraftstoff[T.Ethanol] -753.9051 1.51e+04 -0.050 0.960 -3.04e+04 2.89e+04
PS 141.3709 3.127 45.204 0.000 135.240 147.502
km -0.0869 0.004 -23.866 0.000 -0.094 -0.080
Erstzulassung 482.5563 48.872 9.874 0.000 386.743 578.370
Verbrauch_l_pro_100km 631.4028 140.165 4.505 0.000 356.611 906.194
Omnibus: 4435.504 Durbin-Watson: 1.800
Prob(Omnibus): 0.000 Jarque-Bera (JB): 423808.392
Skew: 4.457 Prob(JB): 0.00
Kurtosis: 49.364 Cond. No. 6.18e+07


Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 6.18e+07. This might indicate that there are
strong multicollinearity or other numerical problems.

Das Modell hat einen R-Squared von 63,1% und einen Adjusted R-Squared von 63,0% und schneidet somit mittelmäßig ab.
R-squared ist eine Kennzahl zur Beurteilung der Anpassungsgüte eines Modells und nimmt einen Wert zwischen 0% und 100% an. Als Bezugsbasis wird der Durchschnitt verwendet.
In statistischen Methoden wird meist lieber der adjusted R-squared genutzt. Da dieser die degrees of freedom miteinbezieht, lässt sich durch adj. R-squared einen besseren Rückschluss auf die Gesamtpopulation der Datensätze ziehen. Dieser ist meistens etwas schlechter als R-squared.
F-statistics ist die Menge der systematischen Varianz (MSM) geteilt durch die Menge der unsystematischen Varianz (MSR). Die Kennzahl gibt an, in welcher Höhe das Modell die Ergebnisausgabe der abhängigen Variable verbessert hat, verglichen zu Ungenauigkeit im Modell. Je höher F-statistics, desto besser. Mit einem Wert von 781 ist F-Statistics schneidet auch diese Kennzahl mittelmäßig ab.
Verbesserungspotenzial gibt es bei den Featuren:
Die p-Values einer Kraftstoff Kategorien liegen über 0,05% und haben daher statistisch gesehen keinen Einfluss auf den Preis. Ein sinnvoller Schritt wäre hier das Zusammenfassen einiger Kraftstoffarten. Ethanol als Kraftstoff kommt in diesem Datensatz nur einmal vor und sollte für das Modell entfernt werden. Elektro/Diesel und Elektro/Benzin könnten beispielsweise in der Kategorie Hypride zusammengefasst werden. Anschließend kann das Modell nochmal trainiert werden und weitere Verbesserungspotentiale identifiziert werden.

Dies ist jedoch nicht mehr Fokus des Projekts. Diese lineare Regression dient lediglich als Ausblick, was mit diesem Datensatz noch alles möglich gewesen wäre.